/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <plat.h>
#include <printf.h>
#include <eventQ.h>
#include <timer.h>
#include <seos.h>
#include <heap.h>
#include <slab.h>
#include <cpu.h>
#include <osApi.h>
#include <util.h>
#include <atomic_se.h>

#define NO_NODE (TaskIndex)(-1)
#define for_each_task(listHead, task) for (task = osTaskByIdx((listHead)->next); task; task = osTaskByIdx(task->list.next))
#define MAKE_NEW_TID(task) task->tid = ((task->tid + TASK_TID_INCREMENT) & TASK_TID_COUNTER_MASK) | \
                                       (osTaskIndex(task) & TASK_TID_IDX_MASK);
#define TID_TO_TASK_IDX(tid) (tid & TASK_TID_IDX_MASK)

#define FL_TASK_STOPPED 1

#define EVT_SUBSCRIBE_TO_EVT         0x00000000
#define EVT_UNSUBSCRIBE_TO_EVT       0x00000001
#define EVT_DEFERRED_CALLBACK        0x00000002
#define EVT_PRIVATE_EVT              0x00000003

#define EVENT_WITH_ORIGIN(evt, origin)       (((evt) & EVT_MASK) | ((origin) << (32 - TASK_TID_BITS)))
#define EVENT_GET_ORIGIN(evt) ((evt) >> (32 - TASK_TID_BITS))
#define EVENT_GET_EVENT(evt) ((evt) & (EVT_MASK & ~EVENT_TYPE_BIT_DISCARDABLE))

/*
 * Since locking is difficult to do right for adding/removing listeners and such
 * since it can happen in interrupt context and not, and one such operation can
 * interrupt another, and we do have a working event queue, we enqueue all the
 * requests and then deal with them in the main code only when the event bubbles
 * up to the front of the queue. This allows us to not need locks around the
 * data structures.
 */

SET_PACKED_STRUCT_MODE_ON
struct TaskList {
    TaskIndex prev;
    TaskIndex next;
} ATTRIBUTE_PACKED;
SET_PACKED_STRUCT_MODE_OFF

struct Task {
    /* App entry points */
    const struct AppHdr *app;

    /* per-platform app info */
    struct PlatAppInfo platInfo;

    /* for some basic number of subbed events, the array is stored directly here. after that, a heap chunk is used */
    uint32_t subbedEventsInt[MAX_EMBEDDED_EVT_SUBS];
    uint32_t *subbedEvents; /* NULL for invalid tasks */

    struct TaskList list;

    /* task pointer will not change throughout task lifetime,
     * however same task pointer may be reused for a new task; to eliminate the ambiguity,
     * TID is maintained for each task such that new tasks will be guaranteed to receive different TID */
    uint16_t tid;

    uint8_t  subbedEvtCount;
    uint8_t  subbedEvtListSz;
    uint8_t  flags;
    uint8_t  ioCount;

};

struct TaskPool {
    struct Task data[MAX_TASKS];
};

union InternalThing {
    struct {
        uint32_t tid;
        uint32_t evt;
    } evtSub;
    struct {
        OsDeferCbkF callback;
        void *cookie;
    } deferred;
    struct {
        uint32_t evtType;
        void *evtData;
        TaggedPtr evtFreeInfo;
        uint32_t toTid;
    } privateEvt;
    union OsApiSlabItem osApiItem;
};

static struct TaskPool mTaskPool;
static struct EvtQueue *mEvtsInternal;
static struct SlabAllocator* mMiscInternalThingsSlab;
static struct TaskList mFreeTasks;
static struct TaskList mTasks;
static struct Task *mCurrentTask;
static struct Task *mSystemTask;
static TaggedPtr *mCurEvtEventFreeingInfo = NULL; //used as flag for retaining. NULL when none or already retained

static inline void list_init(struct TaskList *l)
{
    l->prev = l->next = NO_NODE;
}

static inline struct Task *osGetCurrentTask()
{
    return mCurrentTask;
}

static struct Task *osSetCurrentTask(struct Task *task)
{
    struct Task *old = mCurrentTask;

    while (true) {
        old = mCurrentTask;

        if (atomicCmpXchg32bits((uint32_t*)&mCurrentTask, (uint32_t)old, (uint32_t)task))
            break;
    }

    return old;
}

// beyond this point, noone shall access mCurrentTask directly

static inline bool osTaskTestFlags(struct Task *task, uint32_t mask)
{
    return (atomicReadByte(&task->flags) & mask) != 0;
}

static inline uint32_t osTaskClrSetFlags(struct Task *task, uint32_t clrMask, uint32_t setMask)
{
    while (true) {
        uint8_t flags = atomicReadByte(&task->flags);
        uint8_t newFlags = (flags & ~clrMask) | setMask;

        if (atomicCmpXchgByte(&task->flags, flags, newFlags))
            return newFlags;
    }
}

static inline uint32_t osTaskAddIoCount(struct Task *task, int32_t delta)
{
    uint8_t count = atomicAddByte(&task->ioCount, delta);

    count += delta; // old value is returned, so we add it again

    return count;
}

static inline uint32_t osTaskGetIoCount(struct Task *task)
{
    return atomicReadByte(&task->ioCount);
}

static inline uint8_t osTaskIndex(struct Task *task)
{
    // we don't need signed diff here: this way we simplify boundary check
    size_t idx = task - &mTaskPool.data[0];
    return idx >= MAX_TASKS || &mTaskPool.data[idx] != task ? NO_NODE : idx;
}

static inline struct Task *osTaskByIdx(size_t idx)
{
    return idx >= MAX_TASKS ? NULL : &mTaskPool.data[idx];
}

uint32_t osGetCurrentTid()
{
    if (osGetCurrentTask() == NULL)
        return 0;

    return osGetCurrentTask()->tid;
}

uint32_t osSetCurrentTid(uint32_t tid)
{
    struct Task *task = osTaskByIdx(TID_TO_TASK_IDX(tid));

    if (task && task->tid == tid) {
        struct Task *preempted = osSetCurrentTask(task);
        return preempted->tid;
    }

    return osGetCurrentTid();
}

static inline struct Task *osTaskListPeekHead(struct TaskList *listHead)
{
    TaskIndex idx = listHead->next;
    return idx == NO_NODE ? NULL : &mTaskPool.data[idx];
}

#ifdef DEBUG
static void dumpListItems(const char *p, struct TaskList *listHead)
{
    int i = 0;
    struct Task *task;

    osLog(LOG_ERROR, "List: %s (%p) [%u;%u]\n",
          p,
          listHead,
          listHead ? listHead->prev : NO_NODE,
          listHead ? listHead->next : NO_NODE
         );

    if (!listHead)
        return;

    for_each_task(listHead, task) {
        osLog(LOG_ERROR, "  item %d: task=%p TID=%04X [%u;%u;%u]\n",
              i,
              task,
              task->tid,
              task->list.prev,
              osTaskIndex(task),
              task->list.next
             );
        ++i;
    }
}

static void dumpTaskList(const char *f, struct Task *task, struct TaskList *listHead)
{
    osLog(LOG_ERROR, "%s: pool: %p; task=%p [%u;%u;%u]; listHead=%p [%u;%u]\n",
          f,
          &mTaskPool,
          task,
          task ? task->list.prev : NO_NODE,
          osTaskIndex(task),
          task ? task->list.next : NO_NODE,
          listHead,
          listHead ? listHead->prev : NO_NODE,
          listHead ? listHead->next : NO_NODE
         );
    dumpListItems("Tasks", &mTasks);
    dumpListItems("Free Tasks", &mFreeTasks);
}
#else
#define dumpTaskList(a,b,c)
#endif

static inline void osTaskListRemoveTask(struct TaskList *listHead, struct Task *task)
{
    if (task && listHead) {
        struct TaskList *cur = &task->list;
        TaskIndex left_idx = cur->prev;
        TaskIndex right_idx = cur->next;
        struct TaskList *left =  left_idx == NO_NODE ? listHead : &mTaskPool.data[left_idx].list;
        struct TaskList *right = right_idx == NO_NODE ? listHead : &mTaskPool.data[right_idx].list;
        cur->prev = cur->next = NO_NODE;
        left->next = right_idx;
        right->prev = left_idx;
    } else {
        dumpTaskList(__func__, task, listHead);
    }
}

static inline void osTaskListAddTail(struct TaskList *listHead, struct Task *task)
{
    if (task && listHead) {
        struct TaskList *cur = &task->list;
        TaskIndex last_idx = listHead->prev;
        TaskIndex new_idx = osTaskIndex(task);
        struct TaskList *last = last_idx == NO_NODE ? listHead : &mTaskPool.data[last_idx].list;
        cur->prev = last_idx;
        cur->next = NO_NODE;
        last->next = new_idx;
        listHead->prev = new_idx;
    } else {
        dumpTaskList(__func__, task, listHead);
    }
}

static struct Task *osAllocTask()
{
    struct Task *task = osTaskListPeekHead(&mFreeTasks);

    if (task) {
        osTaskListRemoveTask(&mFreeTasks, task);
        uint16_t tid = task->tid;
        memset(task, 0, sizeof(*task));
        task->tid = tid;
    }

    return task;
}

static void osFreeTask(struct Task *task)
{
    if (task) {
        task->flags = 0;
        task->ioCount = 0;
        osTaskListAddTail(&mFreeTasks, task);
    }
}

static void osAddTask(struct Task *task)
{
    osTaskListAddTail(&mTasks, task);
}

static inline struct Task* osTaskFindByTid(uint32_t tid)
{
    TaskIndex idx = TID_TO_TASK_IDX(tid);

    return idx < MAX_TASKS ? &mTaskPool.data[idx] : NULL;
}

static inline bool osTaskInit(struct Task *task)
{
    struct Task *preempted = osSetCurrentTask(task);
    bool done = cpuAppInit(task->app, &task->platInfo, task->tid);
    osSetCurrentTask(preempted);
    return done;
}

static inline void osTaskEnd(struct Task *task)
{
    struct Task *preempted = osSetCurrentTask(task);
    uint16_t tid = task->tid;

    cpuAppEnd(task->app, &task->platInfo);

    // task was supposed to release it's resources,
    // but we do our cleanup anyway
    osSetCurrentTask(mSystemTask);
    //platFreeResources(tid); // HW resources cleanup (IRQ, DMA etc)
    //sensorUnregisterAll(tid);
    //timTimerCancelAll(tid);
    heapFreeAll(tid);
    // NOTE: we don't need to unsubscribe from events
    osSetCurrentTask(preempted);
}

static inline void osTaskHandle(struct Task *task, uint32_t evtType, const void* evtData)
{
    struct Task *preempted = osSetCurrentTask(task);
    cpuAppHandle(task->app, &task->platInfo, evtType, evtData);
    osSetCurrentTask(preempted);
}

static void handleEventFreeing(uint32_t evtType, void *evtData, uintptr_t evtFreeData) // watch out, this is synchronous
{
    if ((taggedPtrIsPtr(evtFreeData) && !taggedPtrToPtr(evtFreeData)) ||
            (taggedPtrIsUint(evtFreeData) && !taggedPtrToUint(evtFreeData)))
        return;

    if (taggedPtrIsPtr(evtFreeData))
        ((EventFreeF)taggedPtrToPtr(evtFreeData))(evtData);
    else {
        struct AppEventFreeData fd = {.evtType = evtType, .evtData = evtData};
        struct Task* task = osTaskFindByTid(taggedPtrToUint(evtFreeData));

        if (!task)
            osLog(LOG_ERROR, "EINCEPTION: Failed to find app to call app to free event sent to app(s).\n");
        else
            osTaskHandle(task, EVT_APP_FREE_EVT_DATA, &fd);
    }
}

static void osInit(void)
{
    heapInit();
    //platInitialize();

    osLog(LOG_INFO, "SEOS Initializing\n");
    //cpuInitLate();

    /* create the queues */
    if (!(mEvtsInternal = evtQueueAlloc(512, handleEventFreeing))) {
        osLog(LOG_INFO, "events failed to init\n");
        return;
    }

    mMiscInternalThingsSlab = slabAllocatorNew(sizeof(union InternalThing), alignof(union InternalThing),
                              64 /* for now? */);

    if (!mMiscInternalThingsSlab) {
        osLog(LOG_INFO, "deferred actions list failed to init\n");
        return;
    }
}

static struct Task* osTaskFindByAppID(uint64_t appID)
{
    struct Task *task;

    for_each_task(&mTasks, task) {
        if (task->app && task->app->hdr.appId == appID)
            return task;
    }

    return NULL;
}

static inline bool osAppIsValid(const struct AppHdr *app)
{
    return app->hdr.magic == APP_HDR_MAGIC &&
           app->hdr.fwVer == APP_HDR_VER_CUR &&
           (app->hdr.fwFlags & FL_APP_HDR_APPLICATION) != 0 &&
           app->hdr.payInfoType == LAYOUT_APP;
}

static bool osIntAppIsValid(const struct AppHdr *app)
{
    return  osAppIsValid(app) &&
            (app->hdr.fwFlags & FL_APP_HDR_INTERNAL) != 0;
}
static struct Task *osLoadApp(const struct AppHdr *app)
{
    struct Task *task;

    task = osAllocTask();

    if (!task) {
        osLog(LOG_WARN, "External app id %016" PRIX64 " @ %p cannot be used as too many apps already exist.\n", app->hdr.appId,
              app);
        return NULL;
    }

    task->app = app;
    bool done = (app->hdr.fwFlags & FL_APP_HDR_INTERNAL) ?
                cpuInternalAppLoad(task->app, &task->platInfo) :
                false;

    if (!done) {
        osLog(LOG_WARN, "App @ %p ID %016" PRIX64 " failed to load\n", app, app->hdr.appId);
        osFreeTask(task);
        task = NULL;
    }

    return task;
}

static void osUnloadApp(struct Task *task)
{
    // this is called on task that has stopped running, or had never run
    //cpuAppUnload(task->app, &task->platInfo);
    osFreeTask(task);
}

static bool osStartApp(const struct AppHdr *app)
{
    bool done = false;
    struct Task *task;

    if ((task = osLoadApp(app)) != NULL) {
        task->subbedEvtListSz = MAX_EMBEDDED_EVT_SUBS;
        task->subbedEvents = task->subbedEventsInt;
        MAKE_NEW_TID(task);

        done = osTaskInit(task);

        if (!done) {
            osLog(LOG_WARN, "App @ %p ID %016" PRIX64 "failed to init\n", task->app, task->app->hdr.appId);
            osUnloadApp(task);
        } else {
            osAddTask(task);
        }
    }

    return done;
}
static void osStartTasks(void)
{
    const struct AppHdr *app;
    uint32_t i, nApps;
    struct Task* task;
    uint32_t taskCnt = 0;

    osLog(LOG_DEBUG, "Initializing task pool...\n");
    list_init(&mTasks);
    list_init(&mFreeTasks);

    for (i = 0; i < MAX_TASKS; ++i) {
        task = &mTaskPool.data[i];
        list_init(&task->list);
        osFreeTask(task);
    }

    mSystemTask = osAllocTask(); // this is a dummy task; holder of TID 0; all system code will run with TID 0
    osSetCurrentTask(mSystemTask);
    osLog(LOG_DEBUG, "System task is: %p\n", mSystemTask);

    /* first enum all internal apps, making sure to check for dupes */
    osLog(LOG_DEBUG, "Starting internal apps...\n");

    for (i = 0, app = platGetInternalAppList(&nApps); i < nApps; i++, app++) {
        if (!osIntAppIsValid(app)) {
            osLog(LOG_WARN, "Invalid internal app @ %p ID %016" PRIX64
                  "header version: %" PRIu16
                  "\n",
                  app, app->hdr.appId, app->hdr.fwVer);
            continue;
        }

        if (!(app->hdr.fwFlags & FL_APP_HDR_INTERNAL)) {
            osLog(LOG_WARN, "Internal app is not marked: [%p]: flags: 0x%04" PRIX16
                  "; ID: %016" PRIX64
                  "; ignored\n",
                  app, app->hdr.fwFlags, app->hdr.appId);
            continue;
        }

        if ((task = osTaskFindByAppID(app->hdr.appId))) {
            osLog(LOG_WARN, "Internal app ID %016" PRIX64
                  "@ %p attempting to update internal app @ %p; app @%p ignored.\n",
                  app->hdr.appId, app, task->app, app);
            continue;
        }

        if (osStartApp(app))
            taskCnt++;
    }

    osLog(LOG_DEBUG, "Started %" PRIu32 " internal apps;\n", taskCnt);
}

static void osInternalEvtHandle(uint32_t evtType, void *evtData)
{
    union InternalThing *da = (union InternalThing*)evtData;
    struct Task *task;
    uint32_t i;

    switch (evtType) {
        case EVT_SUBSCRIBE_TO_EVT:
        case EVT_UNSUBSCRIBE_TO_EVT:
            /* get task */
            task = osTaskFindByTid(da->evtSub.tid);

            if (!task)
                break;

            /* find if subscribed to this evt */
            for (i = 0; i < task->subbedEvtCount && task->subbedEvents[i] != da->evtSub.evt; i++);

            /* if unsub & found -> unsub */
            if (evtType == EVT_UNSUBSCRIBE_TO_EVT && i != task->subbedEvtCount)
                task->subbedEvents[i] = task->subbedEvents[--task->subbedEvtCount];
            /* if sub & not found -> sub */
            else if (evtType == EVT_SUBSCRIBE_TO_EVT && i == task->subbedEvtCount) {
                if (task->subbedEvtListSz == task->subbedEvtCount) { /* enlarge the list */
                    uint32_t newSz = (task->subbedEvtListSz * 3 + 1) / 2;
                    uint32_t *newList = heapAlloc(sizeof(uint32_t[newSz])); /* grow by 50% */

                    if (newList) {
                        memcpy(newList, task->subbedEvents, sizeof(uint32_t[task->subbedEvtListSz]));

                        if (task->subbedEvents != task->subbedEventsInt)
                            heapFree(task->subbedEvents);

                        task->subbedEvents = newList;
                        task->subbedEvtListSz = newSz;
                    }
                }

                if (task->subbedEvtListSz > task->subbedEvtCount) { /* have space ? */
                    task->subbedEvents[task->subbedEvtCount++] = da->evtSub.evt;
                }
            }

            break;

        case EVT_APP_END:
            task = evtData;
            osTaskEnd(task);
            osUnloadApp(task);
            break;

        case EVT_DEFERRED_CALLBACK:
            da->deferred.callback(da->deferred.cookie);
            break;

        case EVT_PRIVATE_EVT:
            task = osTaskFindByTid(da->privateEvt.toTid);

            if (task) {
                //private events cannot be retained
                TaggedPtr *tmp = mCurEvtEventFreeingInfo;
                mCurEvtEventFreeingInfo = NULL;

                osTaskHandle(task, da->privateEvt.evtType, da->privateEvt.evtData);
                mCurEvtEventFreeingInfo = tmp;
            }

            handleEventFreeing(da->privateEvt.evtType, da->privateEvt.evtData, da->privateEvt.evtFreeInfo);
            break;
    }
}

void abort(void)
{
    /* this is necessary for va_* funcs... */
    osLog(LOG_ERROR, "Abort called");

    while (1);
}

bool osRetainCurrentEvent(TaggedPtr *evtFreeingInfoP)
{
    if (!mCurEvtEventFreeingInfo)
        return false;

    *evtFreeingInfoP = *mCurEvtEventFreeingInfo;
    mCurEvtEventFreeingInfo = NULL;
    return true;
}

void osFreeRetainedEvent(uint32_t evtType, void *evtData, TaggedPtr *evtFreeingInfoP)
{
    handleEventFreeing(evtType, evtData, *evtFreeingInfoP);
}

void osMainInit(void)
{
    //cpuInit();
    cpuIntsOff();
    osInit();
    timInit();
    //sensorsInit();
    //syscallInit();
    //osApiExport(mMiscInternalThingsSlab);
    //apIntInit();
    cpuIntsOn();
    osStartTasks();

    //broadcast app start to all already-loaded apps
    (void)osEnqueueEvt(EVT_APP_START, NULL, NULL);
}

void osMainDequeueLoop(void)
{
    TaggedPtr evtFreeingInfo;
    uint32_t evtType, j;
    void *evtData;
    struct Task *task;
    uint16_t tid;

    /* get an event */
    if (!evtQueueDequeue(mEvtsInternal, &evtType, &evtData, &evtFreeingInfo, false))
        return;

    evtType = EVENT_GET_EVENT(evtType);
    tid = EVENT_GET_ORIGIN(evtType);
    task = osTaskFindByTid(tid);

    if (task)
        osTaskAddIoCount(task, -1);

    /* by default we free them when we're done with them */
    mCurEvtEventFreeingInfo = &evtFreeingInfo;

    if (evtType < EVT_NO_FIRST_USER_EVENT) {
        /* handle deferred actions and other reserved events here */
        osInternalEvtHandle(evtType, evtData);
    } else {
        /* send this event to all tasks who want it */
        for_each_task(&mTasks, task) {
            for (j = 0; j < task->subbedEvtCount; j++) {
                if (task->subbedEvents[j] == evtType) {
                    osTaskHandle(task, evtType, evtData);
                    break;
                }
            }
        }
    }

    /* free it */
    if (mCurEvtEventFreeingInfo)
        handleEventFreeing(evtType, evtData, evtFreeingInfo);

    /* avoid some possible errors */
    mCurEvtEventFreeingInfo = NULL;
}

void osMain(void)
{
    osMainInit();

    while (true) {
        osMainDequeueLoop();
    }
}

static void osDeferredActionFreeF(void* event)
{
    slabAllocatorFree(mMiscInternalThingsSlab, event);
}

static bool osEventSubscribeUnsubscribe(uint32_t tid, uint32_t evtType, bool sub)
{
    union InternalThing *act = slabAllocatorAlloc(mMiscInternalThingsSlab);

    if (!act)
        return false;

    act->evtSub.evt = evtType;
    act->evtSub.tid = tid;

    return osEnqueueEvtOrFree(sub ? EVT_SUBSCRIBE_TO_EVT : EVT_UNSUBSCRIBE_TO_EVT, act, osDeferredActionFreeF);
}

bool osEventSubscribe(uint32_t tid, uint32_t evtType)
{
    (void)tid;
    return osEventSubscribeUnsubscribe(osGetCurrentTid(), evtType, true);
}

bool osEventUnsubscribe(uint32_t tid, uint32_t evtType)
{
    (void)tid;
    return osEventSubscribeUnsubscribe(osGetCurrentTid(), evtType, false);
}

static bool osEnqueueEvtCommon(uint32_t evtType, void *evtData, TaggedPtr evtFreeInfo)
{
    struct Task *task = osGetCurrentTask();

    if (osTaskTestFlags(task, FL_TASK_STOPPED)) {
        handleEventFreeing(evtType, evtData, evtFreeInfo);
        return true;
    }

    evtType = EVENT_WITH_ORIGIN(evtType, osGetCurrentTid());
    osTaskAddIoCount(task, 1);

    if (evtQueueEnqueue(mEvtsInternal, evtType, evtData, evtFreeInfo, false))
        return true;

    osTaskAddIoCount(task, -1);
    return false;
}

bool osEnqueueEvt(uint32_t evtType, void *evtData, EventFreeF evtFreeF)
{
    return osEnqueueEvtCommon(evtType, evtData, taggedPtrMakeFromPtr(evtFreeF));
}

bool osEnqueueEvtOrFree(uint32_t evtType, void *evtData, EventFreeF evtFreeF)
{
    bool success = osEnqueueEvt(evtType, evtData, evtFreeF);

    if (!success && evtFreeF)
        evtFreeF(evtData);

    return success;
}

bool osEnqueueEvtAsApp(uint32_t evtType, void *evtData, uint32_t fromAppTid)
{
    // compatibility with existing external apps
    if (evtType & EVENT_TYPE_BIT_DISCARDABLE_COMPAT)
        evtType |= EVENT_TYPE_BIT_DISCARDABLE;

    (void)fromAppTid;
    return osEnqueueEvtCommon(evtType, evtData, taggedPtrMakeFromUint(osGetCurrentTid()));
}

bool osDefer(OsDeferCbkF callback, void *cookie, bool urgent)
{
    union InternalThing *act = slabAllocatorAlloc(mMiscInternalThingsSlab);

    if (!act)
        return false;

    act->deferred.callback = callback;
    act->deferred.cookie = cookie;

    if (evtQueueEnqueue(mEvtsInternal, EVT_DEFERRED_CALLBACK, act, taggedPtrMakeFromPtr(osDeferredActionFreeF), urgent))
        return true;

    slabAllocatorFree(mMiscInternalThingsSlab, act);
    return false;
}

static bool osEnqueuePrivateEvtEx(uint32_t evtType, void *evtData, TaggedPtr evtFreeInfo, uint32_t toTid)
{
    union InternalThing *act = slabAllocatorAlloc(mMiscInternalThingsSlab);

    if (!act)
        return false;

    act->privateEvt.evtType = evtType;
    act->privateEvt.evtData = evtData;
    act->privateEvt.evtFreeInfo = evtFreeInfo;
    act->privateEvt.toTid = toTid;

    return osEnqueueEvtOrFree(EVT_PRIVATE_EVT, act, osDeferredActionFreeF);
}

bool osEnqueuePrivateEvt(uint32_t evtType, void *evtData, EventFreeF evtFreeF, uint32_t toTid)
{
    return osEnqueuePrivateEvtEx(evtType, evtData, taggedPtrMakeFromPtr(evtFreeF), toTid);
}

bool osEnqueuePrivateEvtAsApp(uint32_t evtType, void *evtData, uint32_t fromAppTid, uint32_t toTid)
{
    (void)fromAppTid;
    return osEnqueuePrivateEvtEx(evtType, evtData, taggedPtrMakeFromUint(osGetCurrentTid()), toTid);
}

bool osTidById(uint64_t appId, uint32_t *tid)
{
    struct Task *task;

    for_each_task(&mTasks, task) {
        if (task->app && task->app->hdr.appId == appId) {
            *tid = task->tid;
            return true;
        }
    }

    return false;
}

bool osAppInfoById(uint64_t appId, uint32_t *appIdx, uint32_t *appVer, uint32_t *appSize)
{
    uint32_t i = 0;
    struct Task *task;

    for_each_task(&mTasks, task) {
        const struct AppHdr *app = task->app;

        if (app && app->hdr.appId == appId) {
            *appIdx = i;
            *appVer = app->hdr.appVer;
            *appSize = app->sect.rel_end;
            return true;
        }

        i++;
    }

    return false;
}

bool osAppInfoByIndex(uint32_t appIdx, uint64_t *appId, uint32_t *appVer, uint32_t *appSize)
{
    struct Task *task;
    int i = 0;

    for_each_task(&mTasks, task) {
        if (i != appIdx) {
            ++i;
        } else {
            const struct AppHdr *app = task->app;
            *appId = app->hdr.appId;
            *appVer = app->hdr.appVer;
            *appSize = app->sect.rel_end;
            return true;
        }
    }

    return false;
}
